﻿using Common.Core.Cache;
using Common.Core.Extensions;

namespace Common.Helpers
{
    /// <summary>
    /// 分布式流水号生成类。依赖于redis
    /// 此类生成方法是时间戳+递增。每秒GetIdentity()支持最大26万个流水号。
    /// 如果需要更多流水号，请用GetIdentity(int idLength),idLength为14位时可以生成16384个，为16位时可以生成6万，为18位时可以生成26万，为20位时可以生成100万
    /// </summary>
    public sealed class IdentityNoRedis
    {
        private static readonly DateTime DefStartTime = new DateTime(2000, 1, 1, 0, 0, 0, 0);
        private static readonly object LockObject = new object();

        /// <summary>
        /// IdentityNoRedisGenerator的静态实例
        /// </summary>
        private volatile static IdentityNoRedis _ig;
        public static IdentityNoRedis Instance()
        {
            if (_ig == null)
            {
                lock (LockObject)
                {
                    if (_ig == null)
                    {
                        _ig = new IdentityNoRedis();
                    }
                }
            }
            return _ig;
        }

        /// <summary>
        /// 按照时间戳刻度计算当前时间戳
        /// </summary>
        /// <param name="startDateTime">起始时间</param>
        /// <param name="timestampStyleTicks">时间戳刻度值</param>
        /// <returns>long</returns>
        private static long GetTimestamp(DateTime startDateTime, long timestampStyleTicks)
        {
            DateTime endDateTime = DateTime.Now;
            long ticks = (endDateTime.Ticks - startDateTime.Ticks) / timestampStyleTicks;
            return ticks;
        }

        #region [ 快捷获取Id方法 ]
        /// <summary>
        /// 计算一个秒刻度的14位Id，每秒最多生成16,384个Id
        /// </summary>
        /// <returns>long</returns>
        public static long Get14Id()
        {
            return Instance().GetIdentity(14);
        }

        /// <summary>
        /// 计算一个秒刻度的16位Id，每秒最多生成65,536个Id
        /// </summary>
        /// <returns>long</returns>
        public static long Get16Id()
        {
            return Instance().GetIdentity(16);
        }

        /// <summary>
        /// 计算一个秒刻度的18位Id，每秒最多生成262,144个Id
        /// </summary>
        /// <returns>long</returns>
        public static long Get18Id()
        {
            return Instance().GetIdentity(18);
        }

        /// <summary>
        /// 计算一个秒刻度的20位Id，每秒最多生成1,048,576个Id
        /// </summary>
        /// <returns>long</returns>
        public static long Get20Id()
        {
            return Instance().GetIdentity(20);
        }
        #endregion

        /// <summary>
        /// 计算一个Id，按秒刻度生成18长度的Id
        /// </summary>
        /// <returns>long</returns>
        public long GetIdentity()
        {
            return GetIdentity(DefStartTime, TimestampStyle.SecondTicks, 18);
        }

        /// <summary>
        /// 计算一个Id，按指定刻度生成18长度的Id
        /// </summary>
        /// <param name="timestampStyle">时间戳刻度</param>
        /// <returns>long</returns>
        public long GetIdentity(TimestampStyle timestampStyle)
        {
            return GetIdentity(DefStartTime, timestampStyle, 18);
        }

        /// <summary>
        /// 计算一个Id，在秒刻度上按指定长度生成Id
        /// </summary>
        /// <param name="idLength">流水号二进制长度</param>
        /// <returns>long</returns>
        public long GetIdentity(int idLength)
        {
            return GetIdentity(DefStartTime, TimestampStyle.SecondTicks, idLength);
        }

        /// <summary>
        /// 计算一个Id，需指定刻度和长度
        /// </summary>
        /// <param name="timestampStyle">时间戳刻度</param>
        /// <param name="idLength">流水号二进制长度</param>
        /// <returns>long</returns>
        public long GetIdentity(TimestampStyle timestampStyle, int idLength)
        {
            return GetIdentity(DefStartTime, timestampStyle, idLength);
        }

        /// <summary>
        /// 计算一个Id，需指定刻度和长度及指定起始时间
        /// </summary>
        /// <param name="startDateTime">时间戳的起始时间</param>
        /// <param name="timestampStyle">时间戳刻度</param>
        /// <param name="idLength">流水号二进制长度</param>
        /// <returns>long</returns>
        public long GetIdentity(DateTime startDateTime, TimestampStyle timestampStyle, int idLength)
        {
            long timestampStyleTicks = long.Parse(timestampStyle.ToString("D"));
            return GetIdentity(startDateTime, timestampStyleTicks, idLength);
        }

        /// <summary>
        /// 计算一个Id
        /// </summary>
        /// <param name="startDateTime">时间戳的起始时间</param>
        /// <param name="timestampStyleTicks">时间戳刻度(毫微秒单位)</param>
        /// <param name="idLength">流水号二进制长度</param>
        /// <returns>long</returns>
        private long GetIdentity(DateTime startDateTime, long timestampStyleTicks, int idLength)
        {
            return getIdentityRedis(startDateTime, timestampStyleTicks, idLength);
        }

        /// <summary>
        /// 计算一个Id
        /// </summary>
        /// <param name="startDateTime">时间戳的起始时间</param>
        /// <param name="timestampStyleTicks">时间戳刻度(毫微秒单位)</param>
        /// <param name="idLength">流水号二进制长度</param>
        /// <returns>long</returns>
        private long getIdentityRedis(DateTime startDateTime, long timestampStyleTicks, int idLength)
        {
            if (idLength <= 1)
                throw new Exception("流水号二进制长度指派错误，长度不能为0或负数");

            // 计算最大末尾数据
            var maxNo = Math.Pow(2, idLength);

            //取得时间戳（当前时间按刻度取值）
            long timestampTick = GetTimestamp(startDateTime, timestampStyleTicks);

            // 记录最后一个计数器值
            long _lastEndNo = new CacheHelper().StringIncrement($"MyClassLibraryCommon:IdentityNoRedis:{timestampTick}:{idLength}", 1, TimeSpan.FromSeconds(5)).ToLong();

            // 取位移后数据，方便填充
            var timestamp = timestampTick << idLength;

            // 判断末尾数据是否超标
            if (_lastEndNo > maxNo)
                throw new Exception(string.Format("在{0}毫秒内生成ID已经超过{1}个", timestampTick, maxNo));

            //生成完整结果（Id）
            long result = timestamp | _lastEndNo;

            return result;
        }
    }
}
